// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/bittrex/order_v3/parser.h"

#include "coin2/base/base64.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/zlib_decompress.h"
#include "coin2/exchange/bittrex/order_v3/error_code.h"

namespace coin2::exchange::bittrex::order_v3 {

int64_t BittrexParser::GetProcOrderId(const JsonValue& data) {
  if (!data.HasMember("clientOrderId") || !data["clientOrderId"].IsString() ||
      data["clientOrderId"].IsNull()) {
    return 0;
  }

  // "clientOrderId":"00000000-0000-0168-9759-148899586388",
  auto splitted = Split(data["clientOrderId"].GetString(), "-");

  if (splitted.size() != 5) {
    return 0;
  }

  auto whole_client_order_id = splitted[2] + splitted[3] + splitted[4];

  return stoll(whole_client_order_id);
}

coin::proto::OrderSide BittrexParser::GetOrderSide(const std::string& side) {
  if (side == "BUY") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "SELL") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

std::string BittrexParser::GetFeeCurrency(const std::string& symbol) {
  std::vector<std::string> splitted = Split(symbol, "-");

  CHECK_EQ(splitted.size(), 2) << "Unexpected symbol format! symbol:" << symbol;

  return splitted[1];
}

coin::proto::OrderType BittrexParser::GetOrderType(const std::string& type) {
  if (type == "LIMIT" || type == "CEILING_LIMIT") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "MARKET" || type == "CEILING_MARKET") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void BittrexParser::SetOrderStatus(ProductOrderElement* order, const JsonValue& data) {
  const std::string status = data["status"].GetString();

  if (status == "OPEN") {
    order->set_is_live(true);
    return;
  }

  if (status == "CLOSED") {
    const double quantity = stod(data["quantity"].GetString());
    const double fill_quantity = stod(data["fillQuantity"].GetString());

    order->set_is_live(false);
    if (quantity == fill_quantity) {
      order->set_fully_filled(true);
    } else {
      order->set_cancel_confirmed(true);
    }
    return;
  }

  LOG(ERROR) << "[SetOrderStatus] Unknown order status: " << status;
}

void BittrexParser::ParseAccountBalance() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseAccountBalance] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(doc);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BittrexParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Bittrex");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      *balance = std::move(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BittrexParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  [
      {
          "currencySymbol":"BTC",
          "total":"0.00000000",
          "available":"0.00000000",
          "updatedAt":"2023-07-14T02:18:00.77Z"
      },
      {
          "currencySymbol":"BTXCRD",
          "total":"1500.00000000",
          "available":"1500.00000000",
          "updatedAt":"2023-06-20T16:44:45.71Z"
      },
      {
          "currencySymbol":"USDT",
          "total":"293.55000000",
          "available":"293.55000000",
          "updatedAt":"2023-07-14T02:32:50.84Z"
      }
  ]
  */
  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc.GetArray()) {
    if (!data.HasMember("currencySymbol") || !data.HasMember("total") ||
        !data.HasMember("available")) {
      continue;
    }
    std::string currency(data["currencySymbol"].GetString());
    CurrencyBalance& currency_balance = (*raw_balance_map)[currency];
    currency_balance.set_currency_native(std::move(currency));
    auto avail = stod(data["available"].GetString());
    auto total = stod(data["total"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_total(total);
    currency_balance.set_hold(total - avail);
  }

  return true;
}

void BittrexParser::ParseOpenOrderInfo() { ParseOrderInfos(__func__); }

void BittrexParser::ParseHistoryOrderInfo() { ParseOrderInfos(__func__); }

void BittrexParser::ParseOrderInfos(std::string func_name) {
  /*
  [
      {
          "id":"6a310fff-c94a-47f8-b252-b5419869944f",
          "marketSymbol":"XRP-USDT",
          "direction":"SELL",
          "type":"LIMIT",
          "quantity":"6.99560000",
          "limit":"0.781286700000",
          "timeInForce":"POST_ONLY_GOOD_TIL_CANCELLED",
          "clientOrderId":"00000000-0000-0168-9759-148899586388",
          "fillQuantity":"0.00000000",
          "commission":"0.00000000",
          "proceeds":"0.00000000",
          "status":"OPEN",
          "createdAt":"2023-07-19T09:43:17.51Z",
          "updatedAt":"2023-07-19T09:43:17.51Z"
      }
  ]
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[" + func_name + "] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[" + func_name + "] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    if (!data.HasMember("marketSymbol")) {
      continue;
    }
    std::string native_symbol = data["marketSymbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BittrexParser::ParseOrderInfo(ProductOrderElement* order, const JsonValue& data) {
  order->set_exchange_type(ExchangeType::Bittrex);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v3");
  order->set_exchange_order_id(data["id"].GetString());
  order->set_symbol(data["marketSymbol"].GetString());
  order->set_price(stod(data["limit"].GetString()));
  order->set_qty(std::fabs(stod(data["quantity"].GetString())));
  order->set_proc_order_id(GetProcOrderId(data));
  order->set_side(GetOrderSide(data["direction"].GetString()));
  order->set_order_type(GetOrderType(data["type"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, data);
}

void BittrexParser::ParseFillOrderInfo() {
  /*
  [
      {
          "id":"a1840c98-403f-46fe-ab21-49acefc785ab",
          "marketSymbol":"BTC-USDT",
          "executedAt":"2023-07-19T09:03:32.74Z",
          "quantity":"0.00020000",
          "rate":"29893.975462000000",
          "orderId":"8dd3fd12-03b0-435c-8d9f-bf3c622c5840",
          "commission":"0.00000000",
          "isTaker":false,
          "direction":"BUY"
      }
  ]
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseFillOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc.GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseFillInfo(fill, data);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void BittrexParser::ParseFillInfo(ProductFillElement* fill, const JsonValue& data) {
  std::string symbol = data["marketSymbol"].GetString();
  fill->set_symbol(symbol);
  fill->set_exchange_order_id(data["orderId"].GetString());
  fill->set_price(stod(data["rate"].GetString()));
  fill->set_qty(std::fabs(stod(data["quantity"].GetString())));
  fill->set_fill_id(data["id"].GetString());
  fill->set_side(GetOrderSide(data["direction"].GetString()));
  fill->set_fee(stod(data["commission"].GetString()));
  fill->set_fee_currency(GetFeeCurrency(symbol));
  fill->set_fill_type(
      data["isTaker"].GetBool() ? coin::proto::FillType::TAKER_FILL_TYPE
                                : coin::proto::FillType::MAKER_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());
}

void BittrexParser::ParseSubmitResponse() {
  /*
  {
      "id":"b37ef435-9a43-479b-8af9-5b3e2eacc252",
      "marketSymbol":"BTC-USDT",
      "direction":"BUY",
      "type":"LIMIT",
      "quantity":"0.00020000",
      "limit":"29896.77462213",
      "timeInForce":"POST_ONLY_GOOD_TIL_CANCELLED",
      "clientOrderId":"00000000-0000-0168-9757-149095380976",
      "fillQuantity":"0.00000000",
      "commission":"0.00000000",
      "proceeds":"0.00000000",
      "status":"OPEN",
      "createdAt":"2023-07-19T09:02:39.54Z",
      "updatedAt":"2023-07-19T09:02:39.54Z"
  }
  */

  /*
  {
      "code":"BAD_REQUEST",
      "detail":"Refer to the data field for specific field validation failures or extra fields...",
      "data":{
          "newOrder.clientOrderId":[
              "Error converting value \"123456789\" to type 'System.Nullable`1[System.Guid]'..."
          ]
      }
  }
  */

  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto exchange_error_msg = GetErrorMsg(json);

  if (exchange_error_code || exchange_error_msg) {
    /*
    special handle for only have code
    {
        "code":"INSUFFICIENT_FUNDS"
    }
    */
    auto error_code_str = exchange_error_code ? exchange_error_code.value() : "";
    auto error_msg_str = exchange_error_msg ? exchange_error_msg.value() : "";

    auto error_code = GetOrderErrorCode(error_code_str);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(error_code_str);
    resp->set_exchange_error_msg(error_msg_str);
  }

  if (resp->error_code() != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("id") || !json["id"].IsString()) {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  resp->set_exchange_order_id(json["id"].GetString());
  resp->set_success(true);
}

void BittrexParser::ParseCancelResponse() {
  /*
  {
      "id":"df89ea8a-a91e-4865-bacc-12cb7a5ab546",
      "marketSymbol":"XRP-USDT",
      "direction":"BUY",
      "type":"LIMIT",
      "quantity":"7.00000000",
      "limit":"0.781172990000",
      "timeInForce":"POST_ONLY_GOOD_TIL_CANCELLED",
      "clientOrderId":"00000000-0000-0168-9758-948927545746",
      "fillQuantity":"0.00000000",
      "commission":"0.00000000",
      "proceeds":"0.00000000",
      "status":"CLOSED",
      "createdAt":"2023-07-19T09:30:14.21Z",
      "updatedAt":"2023-07-19T09:30:18.21Z",
      "closedAt":"2023-07-19T09:30:18.21Z"
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto exchange_error_msg = GetErrorMsg(json);

  if (exchange_error_code || exchange_error_msg) {
    auto error_code_str = exchange_error_code ? exchange_error_code.value() : "";
    auto error_msg_str = exchange_error_msg ? exchange_error_msg.value() : "";

    auto error_code = GetOrderErrorCode(error_code_str);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(error_code_str);
    resp->set_exchange_error_msg(error_msg_str);
  }

  if (resp->error_code() != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("id") || !json.HasMember("status")) {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  resp->set_exchange_order_id(json["id"].GetString());
  resp->set_success(true);

  if (std::string(json["status"].GetString()) == "CLOSED") {
    resp->set_cancel_confirmed(true);
  }
}

void BittrexParser::ParseListenKey() {
  /*
  {
      "Url":"/signalr",
      "ConnectionToken":"af/P7afgTCUxBDTV5vqH6glxTh28wXjU/99dPtIDF+q/zlJHSyvO93UDW/kQS2acb3wEnUaGQ+zBFlzkSdMCHkYHYDyOKoYlAqp6Vyw8HwVhmTDx",
      "ConnectionId":"19512624-5010-428d-aee8-94c4aebc583f",
      "KeepAliveTimeout":20,
      "DisconnectTimeout":30,
      "ConnectionTimeout":110,
      "TryWebSockets":true,
      "ProtocolVersion":"2.0",
      "TransportConnectTimeout":5,
      "LongPollDelay":0
  }
  */

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[" << __func__ << "] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  LOG(INFO) << "bittrex/listen_key: " << JsonToString(json);
  if (json.HasMember("ConnectionToken") && json["ConnectionToken"].IsString()) {
    std::string listen_key(ApiSignatureUtil::UrlEncode(json["ConnectionToken"].GetString()));
    mb_->PublishEvent("bittrex/listen_key", &listen_key);
  } else {
    LOG(ERROR) << "Failed to query listenKey" << JsonToString(json);
  }
}

int32_t BittrexParser::ParseErrorMessage(const std::shared_ptr<AsioHttpContext>& http_context) {
  const auto http_code = http_context->res.result_int();
  if (http_code == 401) {
    LOG(ERROR) << "[ParseErrorMessage] Unauthorized-Authentication failed\n" << msg_->DebugString();
  } else if (http_code == 403) {
    LOG(ERROR) << "[ParseErrorMessage] Access denied\n" << msg_->DebugString();
  } else if (http_code == 404) {
    LOG(ERROR) << "[ParseErrorMessage] Request path not found\n" << msg_->DebugString();
  } else if (http_code == 429) {
    // auto enable_time = 5 * 1'000'000'000LL + GetCurrentTimestamp();
    // mb_->PublishEvent("bittrex/enable_time", reinterpret_cast<void*>(enable_time));
    LOG(ERROR) << "[ParseErrorMessage] Rate Limit Exceeded\n" << msg_->DebugString();
  } else if (http_code == 500) {
    LOG(WARNING) << "[ParseErrorMessage] Bittrex service problem\n" << msg_->DebugString();
  }

  return http_code;
}

void BittrexParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  {
      "accountId":"4117b091-b1c0-43ab-8a47-a19ae58a4a98",
      "sequence":52222,
      "delta":{
          "id":"038bc1e9-1721-4c3d-997a-78ee5cbbd237",
          "marketSymbol":"ETH-USDT",
          "direction":"BUY",
          "type":"LIMIT",
          "quantity":"0.00250000",
          "limit":"1910.906896900000",
          "timeInForce":"POST_ONLY_GOOD_TIL_CANCELLED",
          "clientOrderId":"00000000-0000-0168-9840-935332872312",
          "fillQuantity":"0.00000000",
          "commission":"0.00000000",
          "proceeds":"0.00000000",
          "status":"CLOSED",
          "createdAt":"2023-07-20T08:17:34.19Z",
          "updatedAt":"2023-07-20T08:26:49.58Z",
          "closedAt":"2023-07-20T08:26:49.58Z"
      }
  }
  */
  if (!document.HasMember("delta") || !document["delta"].IsObject()) {
    LOG(ERROR) << "[ParseWsOrderInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();

  auto order = product_order_bundle->add_each_order();

  ParseOrderInfo(order, document["delta"]);

  result_.set_type(MsgType::ORDER_STATUS);
}

void BittrexParser::ParseWsTradeInfoList(const JsonDocument& document) {
  /*
  {
      "accountId":"4117b091-b1c0-43ab-8a47-a19ae58a4a98",
      "sequence":63,
      "deltas":[
          {
              "id":"20960973-6f10-4466-bada-42cf02a1d57c",
              "marketSymbol":"XRP-USDT",
              "executedAt":"2023-07-20T09:12:03.42Z",
              "quantity":"7.00000000",
              "rate":"0.806964670000",
              "orderId":"ed809b49-af58-482f-8010-6866a6b311de",
              "commission":"0.00000000",
              "isTaker":false,
              "direction":"SELL"
          },
          {
              "id":"9b21958d-db06-4376-ac48-e7cd9e8d2527",
              "marketSymbol":"XRP-USDT",
              "executedAt":"2023-07-20T09:12:03.42Z",
              "quantity":"7.00000000",
              "rate":"0.807029720000",
              "orderId":"d9d9b4fb-ddb3-478e-9607-03cedf984172",
              "commission":"0.00000000",
              "isTaker":false,
              "direction":"SELL"
          }
      ]
  }
  */
  if (!document.HasMember("deltas") || !document["deltas"].IsArray()) {
    LOG(ERROR) << "[ParseWsTradeInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();

  bool product_fill_bundle_found = false;
  for (const auto& data : document["deltas"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();

    ParseFillInfo(fill, data);

    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void BittrexParser::ParseWsBalanceInfoList(const JsonDocument& document) {
  /*
  {
      "accountId":"4117b091-b1c0-43ab-8a47-a19ae58a4a98",
      "sequence":49883,
      "delta":{
          "currencySymbol":"XRP",
          "total":"7.00000000",
          "available":"7.00000000",
          "updatedAt":"2023-07-20T08:17:33.19Z"
      }
  }
  */
  if (!is_account_balance_initialized_) {
    return;
  }

  if (!document.HasMember("delta") || !document["delta"].IsObject() ||
      !document["delta"].HasMember("currencySymbol") || !document["delta"].HasMember("total") ||
      !document["delta"].HasMember("available")) {
    LOG(ERROR) << "[ParseWsBalanceInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  const auto& delta = document["delta"];

  if (!symcache_->has_currency_native(delta["currencySymbol"].GetString())) {
    return;
  }

  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Bittrex");
  acc_balance->set_market_type("Spot");

  auto* currency = symcache_->GetCurrencyByNative(delta["currencySymbol"].GetString());
  auto* balance = acc_balance->add_each_balance();
  balance->set_currency(currency->currency());
  balance->set_currency_native(currency->native_currency());
  auto avail = stod(delta["available"].GetString());
  auto total = stod(delta["total"].GetString());
  balance->set_available(avail);
  balance->set_total(total);
  balance->set_hold(total - avail);

  result_.set_type(MsgType::ACCOUNT_BALANCE);
}

void BittrexParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document) || !document.IsObject()) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  std::string json_str = msg_->data->data();

  DLOG(INFO) << "OG ws msg: " << json_str;

  if (document.HasMember("I") && document["I"].IsString()) {
    std::string i = document["I"].GetString();
    if (i == "0") {
      CHECK_EQ(json_str.find("Success\":false"), std::string::npos)
          << "Relogin failed, please check! ws msg:" << json_str;
      return;
    } else if (i == "1") {
      CHECK_EQ(json_str.find("Success\":false"), std::string::npos)
          << "Subscribe failed, please check! ws msg:" << json_str;
      return;
    } else {
      LOG(ERROR) << "Unexpected ws msg with I member! ws msg:" << json_str;
    }
  }

  if (!document.HasMember("M") || !document["M"].IsArray()) {
    return;
  }

  for (const auto& m : document["M"].GetArray()) {
    if (!m.HasMember("M") || !m["M"].IsString() || !m.HasMember("A") || !m["A"].IsArray()) {
      continue;
    }

    const std::string channel = m["M"].GetString();

    if (channel == "heartbeat") {
      LOG(INFO) << "receive server ws ping!";
      continue;
    }

    for (const auto& a : m["A"].GetArray()) {
      std::string decoded;
      bool ok = deflate_string(impl::base64_decode(a.GetString()), &decoded);
      if (!ok) {
        LOG(ERROR) << "decompress fail! please check! ws msg:" << json_str;
        continue;
      }

      JsonDocument sub_document;
      sub_document.Parse<rapidjson::kParseNumbersAsStringsFlag>(decoded.c_str());

      if (channel == "order") {
        ParseWsOrderInfoList(sub_document);
      } else if (channel == "balance") {
        ParseWsBalanceInfoList(sub_document);
      } else if (channel == "execution") {
        ParseWsTradeInfoList(sub_document);
      }
    }
  }
}

auto BittrexParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

  ParseErrorMessage(msg.http_context);

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseAccountBalance();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseOpenOrderInfo();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseHistoryOrderInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseFillOrderInfo();
      break;
    case OrderSubTopicId::LISTEN_KEY:
      ParseListenKey();
      break;
    default: {
      LOG(ERROR) << "[ParseRawMessage] Unknown query type\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bittrex::order_v3
